/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/views/camera.h>
#include <journal/app/constants.h>

namespace journal { namespace view {

void camera::ensureMatrixUpdated()
{
    if (!_matrixDirty)
        return;

    // update camera matrix
    m::matrix44 rotation;
    rotation.setRotationXYZ(
        m::degToRad(_angles.x - 90.f),
        m::degToRad(_angles.y),
        m::degToRad(_angles.z)
        );

    // find the camera's position.  this is the forward vector, multiplied by the
    // rotation and then moved back in that direction by the distance offset
    m::vector3d u(0.f, 1.f, 0.f);
    m::vector3d v;
    rotation.apply(u, &v);

    _direction = v;

    // reverse the direction and now this should contain the direction the camera is pointing at
    v.scale(-1.f);
    
    // now move it back by the distance...
    v.scale(_offsetDistance);

    // the position is pointing dead at the center of the coordinate system
    // so apply the x-y offset to move it on that xy plane to where we want it pointing at
    v.x += _offsetXY.x;
    v.y += _offsetXY.y;

    // now v contains the position of the camera
    _position = v;

    m::matrix44 translation;
    translation.setTranslation(m::point3d(
        v.x,
        v.y,
        v.z
        ));

    // vector * rotation * translation (rotate first then translate)
    translation.applyFully(rotation, &_matrix);

    _matrixDirty = false;
}

void camera::apply(const std::shared_ptr<g::iengine>& graphics)
{
    if (!_dirty)
    {
        return;
    }

    _changeNumber++;

    graphics->replaceCurrentCameraMatrix(this->matrix());	

    graphics->replaceCurrentProjectionWithPerspectiveProjection(
        _focus.x, 
        _focus.y, 
        constants::general::nearZ(),
        constants::general::cameraDistance(),
        constants::general::farZ()
        );

    _observerSource.raise(
        [&](const std::shared_ptr<icamera_observer>& observer, void* context)
        {
            observer->onCameraChange(shared_from_this());
        });

    _dirty = false;
}

void camera::setFocus(const m::vector2d& focus)
{
    _dirty = true;
    _focus = focus;

    // recalculate scale
    b::float32 windowWorldWidth = constants::shellService()->windowSize().x / constants::shellService()->logicalDpi();
    _scale = windowWorldWidth / _focus.x;
}

void camera::setScale(const b::float32 scale)
{
    _dirty = true;
    _scale = scale;

    // recalculate focus
    b::float32 windowWorldWidth = constants::shellService()->windowSize().x / constants::shellService()->logicalDpi();
    b::float32 aspectRatioPixel = constants::shellService()->windowSize().x / constants::shellService()->windowSize().y;
    _focus.x = windowWorldWidth / scale;
    _focus.y = _focus.x / aspectRatioPixel;
}
 

void camera::setAngles(const m::vector3d& angles)
{
    _dirty = true;
    _angles = angles;
    _matrixDirty = true;
}

void camera::setOffsetXY(const m::vector2d& offsetXY)
{
    _dirty = true;
    _offsetXY = offsetXY;
    _matrixDirty = true;
}

void camera::setOffsetDistance(const b::float32 offsetDistance)
{
    _dirty = true;
    _offsetDistance = offsetDistance;
    _matrixDirty = true;
}

}} // namespace journal::view